#include "stdafx.h"
#include "textParser.h"
#include "textParserDefinitions.h"

CTextParser::CTextParser( const CTextParserDefinitions& definition )
	: m_definitions( &definition )
	, m_line( -1 )
	, m_level( -1 )
	, m_numErrors( 0 )
{
}

bool CTextParser::Tokenize( const std::string& file, const std::string& txt, std::vector< CToken >& outTokens )
{
	// remove line feeds (\r) so it works crossplatform
	std::string cleanTxt = txt;
	cleanTxt.erase(std::remove(cleanTxt.begin(), cleanTxt.end(), '\r'), cleanTxt.end());

	int state=0,i=0,tempLine=0;
	std::string token, key;
	bool backoff = false;
	char skipChar = 0;
	int bestTokenType = 0;
	int bestTokenStart = 0;

	// Initialize
	m_level = 0;
	m_line = 1;
	m_file = file;
	m_numErrors = false;

	// Empty code
	if ( cleanTxt.empty() )
	{
		return true;
	}

	// Parse
	const int length = ( int )cleanTxt.length();
	for ( int i=0; i <= length; i++)  
	{
		// Go back one char
		if ( backoff )
		{
			i--;
			backoff = false;     
		}                                                                           

		// Get char
		char c = cleanTxt.c_str()[i];

		// Skip char mode
		if ( skipChar )
		{
			// Whitespace char ?
			if ( (c == skipChar) || IsWhitespace(c) )
			{
				skipChar = 0;
				state = 0;      

				// Don't allow line feed when looking for skip char
				if ( IsNewLine( c ) )
				{
					EmitError( "Unexpected new line" );
					continue;
				}

				// Not a whitespace, keep looking
				if ( !IsWhitespace(c) )
				{
					continue;      
				}
			}
		}

		// Parsing state machine
		switch ( state )
		{
			// Base state 
			case 0:
			{
				// Reset token
				token = "";

				// Whitespace char, skip it
				if ( IsWhitespace(c) )
				{
					break;
				}

				// Comment ?
				if ( c == '/' )
				{
					if ( cleanTxt.c_str()[i+1] == '/' )
					{
						tempLine = m_line;
						state = 1; // Single line comment
						break;
					}
					else if ( cleanTxt.c_str()[i+1] == '*' )
					{
						tempLine = m_line;
						state = 2; // Block comment
						break;
					}
				}

				// std::string ?
				if ( c == '\"' )
				{
					tempLine = m_line;
					state = 4; // std::string
					break;      
				}

				// Name         
				if ( c == '\'' )
				{
					tempLine = m_line;
					state = 5; // std::string
					break;      
				}

				// Hmm, it's something bigger to parse :)
				state = 3;
				backoff = true;
				break;                
			}

			// Single line comment
			case 1:
			{
				// Line feed ? (
				if ( IsNewLine( c ) )
				{
					state = 0;
					// skip \n after \r
					// i++;
				}

				break;     
			}

			// Multiline comment
			case 2:
			{ 
				// End ? (     
				if ( (c == '*') && (cleanTxt.c_str()[i+1] == '/') )
				{
					state = 0;
					i++;
				}

				break;
			}

			// std::string
			case 4:
			{
				// End ?
				if ( c == '\"' )
				{
					if ( m_definitions->m_tokenString == 0 )
					{
						EmitError( "Unexpected string literal" );
					}
					else
					{
						EmitToken( outTokens, m_definitions->m_tokenString, token );
					}

					token = "";      
					state = 0;        
					break;
				}

				// Line feed ?
				if ( IsNewLine( c ))
				{
					EmitError( "New line in constant" );
					state = 0; 
					token = "";      
					break;      
				}

				// Add char
				char chr[ 2 ] = { c, 0 };
				token += chr; 
				break;
			}

			// Name
			case 5:
			{
				// End ?
				if ( c == '\'' )
				{
					if ( m_definitions->m_tokenName == 0 )
					{
						EmitError( "Unexpected name literal" );
					}
					else
					{
						EmitToken( outTokens, m_definitions->m_tokenName, token );
					}
					token = "";
					state = 0;        
					break;
				}

				// Line feed ?
				if ( IsNewLine( c ) )
				{
					EmitError( "New line in constant" );
					state = 0; 
					token = "";
					break;      
				}

				// Not a keyword char ?
				if ( !IsNum(c) && !IsAlpha(c) && (c != '.') && (c != '_') && (c != ' ') )
				{
					EmitError( "Name identifier can contain only digits and letters" );
					skipChar = '\'';
					state = 0;
					token = "";      
					break;            
				}

				// Add char
				char str[2] = { c, 0 };
				token += str;
				break;
			}     

			case 3:
			{
				// Keyword
				CToken cur;

				// Initialize best token buf if 
				if ( token.length() == 0 )
				{
					bestTokenType = -1;
					bestTokenStart = i;
				} 

				// Can be a char
				int parsedTokenType = -1;
				if ( IsBreakChar( cleanTxt.c_str()[ bestTokenStart ] ) && ( i == bestTokenStart ) )
				{
					parsedTokenType = 1;	// char
				}  

				// Can be a number ?
				if ( IsIntegerNumber( &cleanTxt.c_str()[ bestTokenStart ], ( i - bestTokenStart ) + 1) )
				{
					parsedTokenType = 2;	// Integer
				}

				// Can be a float number ?
				if ( IsFloatNumber( &cleanTxt.c_str()[ bestTokenStart ], ( i - bestTokenStart ) + 1 ) )
				{
					parsedTokenType = 3;	// Float
				}

				// Can be a keyword ?
				if ( IsKeyword( &cleanTxt.c_str()[ bestTokenStart ], ( i - bestTokenStart ) + 1 ) )
				{
					parsedTokenType = 4; // Keyword
				}

				// Well we got a whitespace char or nothing was matched
				if ( IsWhitespace( c ) || ( parsedTokenType == -1 ) )
				{
					// If we don't have best match it means that there's an error
					if ( bestTokenType == -1 )
					{
						EmitError( "Unexpected \'%c\'", c );
					}
					else
					{
						// Emit parsed token
						if ( bestTokenType == 1 )		// char
						{
							// Get char to Emit
							char c = token.c_str()[0];

							// Count brackets
							if ( c == '{' )
							{
								EmitBracket( true, 0 );
							}
							else if ( c == '(' )
							{
								EmitBracket( true, 1 );
							}
							else if ( c == '[' )
							{
								EmitBracket( true, 2 );
							}
							else if ( c == ']' )
							{
								EmitBracket( false, 2 );
							}
							else if ( c == ')' )
							{
								EmitBracket( false, 1 );
							}
							else if ( c == '}' )
							{
								EmitBracket( false, 0 );
							}

							// Add token
							EmitToken( outTokens, c, token );
						}
						else if ( bestTokenType == 2 )	// Integer
						{
							// Add integer token
							if ( m_definitions->m_tokenInteger == 0 )
							{
								EmitError( "Unexpected integer constant" );
							}
							else
							{
								EmitToken( outTokens, m_definitions->m_tokenInteger, token );
							}
						}
						else if ( bestTokenType == 3 )	// Float
						{
							// Add floating point token
							if ( m_definitions->m_tokenFloat == 0 )
							{
								EmitError( "Unexpected float constant" );
							}
							else
							{
								EmitToken( outTokens, m_definitions->m_tokenFloat, token );
							}
						}
						else if ( bestTokenType == 4 )
						{
							// Known keyword ?
							bool found = false;
							const size_t count = m_definitions->m_keywords.size();
							for ( size_t i=0; i<count; i++ )
							{
								const std::string& keyword = m_definitions->m_keywords[ i ].m_keyword;
								if ( keyword == token )
								{
									EmitToken( outTokens, m_definitions->m_keywords[ i ].m_id, token );
									found = true;
									break;
								}
							}

							// Not a keyword, add as general identifier
							if ( !found )
							{
								if ( m_definitions->m_tokenIdent == 0 )
								{
									EmitError( "Unexpected identifier constant" );
								}
								else
								{
									EmitToken( outTokens, m_definitions->m_tokenIdent, token );   
								}
							}
						}
						else
						{
							abort();
						}

						// Reset token
						token = "";

						// Unparse last char if it was not a whitespace
						if ( !IsWhitespace( c ) )
						{
							backoff = true;        
						}
					}

					// Go back to state 0
					state = 0;
				}
				else
				{     
					// Add char to token
					char str[2] = { c, 0 };
					token += str;

					// Remember best so far
					bestTokenType = parsedTokenType;
				}

				break;            
			}     

			default:
			{
				abort();
				break;
			}
		}      

		int lineSave = m_line;

		// analyze char
		if ( !AnalizeChar( c ))
		{
			int saveLine = m_line;

			// In multi line comment ?
			if ( state == 2 )
			{
				EmitError( "Unexpected end of file before comment at line '%i' was close", tempLine );
			}

			// In string
			if ( state == 4 )
			{
				EmitError( "Unexpected end of file before string at line '%i' was closed", tempLine );
			}

			// In name
			if ( state == 5 )
			{
				EmitError( "Unexpected end of file before name at line '%i' was closed", tempLine );
			}

			// Unmatched brackets ?
			if ( m_brackets.size() )
			{
				char cs[2][3] = {{'}', ')', ']'}, {'{', '(', '['}};
				const Bracket& lastBracket = m_brackets[ m_brackets.size() - 1 ];
				EmitError( "Unexpected end of file found after '%c' at line %i", cs[1][ lastBracket.m_bracket ], lastBracket.m_line );
			}

			// Restore line numbering
			m_line = saveLine;    
			break;
		}    

		// Restore line numbering when backing off
		if ( backoff )
		{
			m_line = lineSave;
		}
	}

	// Error state
	if ( m_numErrors > 0 )
		fprintf( stderr, "Found %d error(s)\n", m_numErrors );

	// Return parsing state, if there were errors it failed
	return m_numErrors == 0;
}

void CTextParser::EmitError( const char*message, ... )
{
	char buf[1024];
	va_list args;

	va_start( args, message );
#ifdef __linux__
	assert( message != nullptr );
	vsprintf( buf, message, args );
#else
	vsprintf_s( buf, sizeof(buf), message, args );
#endif
	va_end( args );

	fprintf( stderr, "%s(%d): error: %s\n", m_file.c_str(), m_line, buf );
	m_numErrors += 1;
}

void CTextParser::EmitToken( std::vector< CToken >& outTokens, int tokenType, const std::string& data )
{
	CToken info;
	info.m_file = m_file;
	info.m_line = m_line;
	info.m_text = data;
	info.m_id = tokenType;
	info.m_number = atof( data.c_str() );
	outTokens.push_back( info );
}

void CTextParser::EmitBracket( bool push, int type )
{
	char charList[2][3] = {{'}', ')', ']'}, {'{', '(', '['}};

	if ( push )
	{
		// Add new bracket
		Bracket bracket;
		bracket.m_bracket = type;
		bracket.m_line = m_line;
		m_brackets.push_back( bracket );

		// Code scope bracket ?
		if ( type == 1 )
		{
			m_level++;
		}
	}
	else
	{
		// Out of scope ?
		if ( !m_brackets.size() )
		{
			EmitError( "Found unexpected '%c'", charList[0][type] );
			return;
		}

		// Code scope
		if ( type == 1 )
		{
			m_level--;
		}

		// Check if matches
		const Bracket& lastBracket = m_brackets.back();
		if ( type != lastBracket.m_bracket )
		{
			// Throw error
			if ( m_line == lastBracket.m_line )
			{
				EmitError( "Unmatched '%c' from line %i", charList[1][ lastBracket.m_bracket ], lastBracket.m_line );
			}
			else
			{
				EmitError( "Unmatched '%c'", charList[1][ lastBracket.m_bracket ] );
			}

			return;   
		}
		else
		{
			// All ok, pop bracket
			m_brackets.pop_back();
		}
	} 
}

bool CTextParser::AnalizeChar( char c )
{
	// End of file, end parsing
	if ( c == '\0' )
	{
		return false; 
	}

	// Line feed, count lines
	if ( IsNewLine( c ))
	{ 
		m_line++;
	}

	// Continue parsing
	return true;
}

bool CTextParser::IsNewLine( char c ) const
{
	return ( c == '\n' );
}

bool CTextParser::IsLineFeed( char c ) const
{
	return ( c == '\r' ); 
}

bool CTextParser::IsNum( char c ) const
{
	return (c >= '0') && (c <= '9'); 
}

bool CTextParser::IsAlpha( char c ) const
{
	return ((c >= 'A') && (c <= 'Z')) || ((c >= 'a') && (c <= 'z'));    
}

bool CTextParser::IsUnicode( char c ) const
{
	// warning: result of comparison of constant 256 with expression of type 'char' is always false
	return c > 0x100;
}

bool CTextParser::IsWhitespace( char c ) const
{
	return c <= ' ';
}

bool CTextParser::IsBreakChar( char c ) const
{
	// Make sure we break before important chars
	const size_t count = m_definitions->m_chars.size();
	for ( size_t i=0; i<count; i++ )
	{
		if ( m_definitions->m_chars[i] == c )
		{
			return true;
		}
	}

	// Not found
	return false;   
}

bool CTextParser::IsIntegerNumber( const char* start, int count ) const
{
	// Minus/Plus
	if ( count >= 1 && (start[0] == '-' || start[0] == '+') )
	{
		start += 1;
		count -= 1;
	}

	// All chars should be numbers
	for ( int i=0; i<count; i++ )
	{
		if ( !IsNum( start[i] ) )
		{
			return false;
		}
	}

	// It is a number
	return true; 
}

bool CTextParser::IsFloatNumber( const char* start, int count ) const
{
	int state=0;
	int nums=0;
	int i=0;

	// Minus/Plus
	if ( count >= 1 && (start[0] == '-' || start[0] == '+') )
	{
		start += 1;
		count -= 1;
	}

	// Parse rest  
	for ( ; i<count; i++ )
	{
		char c = start[i];

		// Parsing state machine
		if ( state == 0 )
		{
			// Number, continue in current state
			if ( IsNum( c ))
			{ 
				nums++; 
				continue;
			}

			// Dot, go to state 1 (parsing other half)
			if ( c == '.' )
			{ 
				state = 1; 
				continue;
			}

			// Invalid char
			return false;
		}
		else if ( state == 1 )
		{
			// Number, continue in current state
			if ( IsNum( c ))
			{ 
				nums++;
				continue;
			}

			// 'f', go to ending state
			if ( c == 'f' )
			{ 
				state = 2;
				continue;
			}

			// Invalid char
			return false;
		}
		else if ( state == 2 )
		{ 
			// Every char here is invalid because 'f' should be last char :)
			return false;
		}    
	}

	// Valid float was passed iff we are in state 1 or 2
	return (state>0) && nums;
}

bool CTextParser::IsKeyword( const char* start, int count ) const
{
	int state=0;
	int i=0;

	// Matches any known keywords ?
	const size_t keywordCount = m_definitions->m_keywords.size();
	for ( size_t j=0; j<keywordCount; j++ )
	{
		// Length not matching
		const auto& keyword = m_definitions->m_keywords[j].m_keyword;
		if ( keyword.length() < (size_t)count )
		{
			continue;
		}

		// Check if fits
		if ( 0 == strncmp( keyword.c_str(), start, count ))
		{
			// Valid known keyword was matched
			return true;
		}
	}

	// Parse rest  
	for ( ;i<count; i++ )
	{
		char c = start[i];

		// Parsing state machine
		if ( state == 0 )
		{
			// Allow '_' char
			if ( c == '_' )
			{ 
				state = 1;
				continue;
			}

			// In state 0 (first char) only letters are allowed
			if ( IsAlpha( c ))
			{ 
				state = 1;
				continue;
			}

			// Invalid char
			return false;
		}
		else if ( state == 1 )
		{
			// Allow '_' char
			if ( c == '_' )
			{
				continue;
			}

			// Other char can be letters or numbers
			if ( IsNum(c) || IsAlpha(c) )
			{
				continue;
			}

			// Invalid char
			return false;
		}    
	}

	// If we're here valid identifier was parsed
	return true; 
}